home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / cupshelpers / cupshelpers.py < prev    next >
Text File  |  2009-10-19  |  27KB  |  808 lines

  1. ## system-config-printer
  2.  
  3. ## Copyright (C) 2006, 2007, 2008, 2009 Red Hat, Inc.
  4. ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
  5. ## Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
  6.  
  7. ## This program is free software; you can redistribute it and/or modify
  8. ## it under the terms of the GNU General Public License as published by
  9. ## the Free Software Foundation; either version 2 of the License, or
  10. ## (at your option) any later version.
  11.  
  12. ## This program is distributed in the hope that it will be useful,
  13. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. ## GNU General Public License for more details.
  16.  
  17. ## You should have received a copy of the GNU General Public License
  18. ## along with this program; if not, write to the Free Software
  19. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  
  21. import cups, pprint, os, tempfile, re, string
  22. import locale
  23. from . import _debugprint
  24.  
  25. class Printer:
  26.     _flags_blacklist = ["options", "local"]
  27.  
  28.     def __init__(self, name, connection, **kw):
  29.         """
  30.         @param name: printer name
  31.         @type name: string
  32.         @param connection: CUPS connection
  33.         @type connection: CUPS.Connection object
  34.         @param kw: printer attributes
  35.         @type kw: dict indexed by string
  36.         """
  37.         self.name = name
  38.         self.connection = connection
  39.         self.class_members = []
  40.         self.update (**kw)
  41.         self._ppd = None # load on demand
  42.  
  43.     def __del__ (self):
  44.         if self._ppd != None:
  45.             os.unlink(self._ppd)
  46.  
  47.     def __repr__ (self):
  48.         return "<cupshelpers.Printer \"%s\">" % self.name
  49.  
  50.     def _expand_flags(self):
  51.  
  52.         def _ascii_lower(str):
  53.             return str.translate(string.maketrans(string.ascii_uppercase,
  54.                                                   string.ascii_lowercase));
  55.  
  56.         prefix = "CUPS_PRINTER_"
  57.         prefix_length = len(prefix)
  58.  
  59.         # loop over cups constants
  60.         for name in cups.__dict__:
  61.             if name.startswith(prefix):
  62.                 attr_name = \
  63.                     _ascii_lower(name[prefix_length:])
  64.                 if attr_name in self._flags_blacklist: continue
  65.                 if attr_name == "class": attr_name = "is_class"
  66.                 # set as attribute
  67.                 setattr(self, attr_name,
  68.                         bool(self.type & getattr(cups, name)))
  69.  
  70.     def update(self, **kw):
  71.         """
  72.         Update object from printer attributes.
  73.  
  74.         @param kw: printer attributes
  75.         @type kw: dict indexed by string
  76.         """
  77.         self.state = kw.get('printer-state', 0)
  78.         self.enabled = self.state != cups.IPP_PRINTER_STOPPED
  79.         self.device_uri = kw.get('device-uri', "")
  80.         self.info = kw.get('printer-info', "")
  81.         self.is_shared = kw.get('printer-is-shared', None)
  82.         self.location = kw.get('printer-location', "")
  83.         self.make_and_model = kw.get('printer-make-and-model', "")
  84.         self.type = kw.get('printer-type', 0)
  85.         self.uri_supported = kw.get('printer-uri-supported', "")
  86.         if type (self.uri_supported) != list:
  87.             self.uri_supported = [self.uri_supported]
  88.         self._expand_flags()
  89.         if self.is_shared is None:
  90.             self.is_shared = not self.not_shared
  91.         del self.not_shared
  92.         self.other_attributes = kw
  93.  
  94.     def getAttributes(self):
  95.         """
  96.         Fetch further attributes for the printer.
  97.  
  98.         Normally only a small set of attributes is fetched.  This
  99.         method is for fetching more.
  100.         """
  101.         attrs = self.connection.getPrinterAttributes(self.name)
  102.         self.attributes = {}
  103.         self.other_attributes = {}
  104.         self.possible_attributes = {
  105.             'landscape' : ('False', ['True', 'False']),
  106.             'page-border' : ('none', ['none', 'single', 'single-thick',
  107.                                      'double', 'double-thick']),
  108.             }
  109.  
  110.         for key, value in attrs.iteritems():
  111.             if key.endswith("-default"):
  112.                 name = key[:-len("-default")]
  113.                 if name in ["job-sheets", "printer-error-policy",
  114.                             "printer-op-policy", # handled below
  115.                             "notify-events", # cannot be set
  116.                             "document-format", # cannot be set
  117.                             "notify-lease-duration"]: # cannot be set
  118.                     continue 
  119.  
  120.                 supported = attrs.get(name + "-supported", None) or \
  121.                             self.possible_attributes.get(name, None) or \
  122.                             ""
  123.  
  124.                 # Convert a list into a comma-separated string, since
  125.                 # it can only really have been misinterpreted as a list
  126.                 # by CUPS.
  127.                 if isinstance (value, list):
  128.                     value = reduce (lambda x, y: x+','+y, value)
  129.  
  130.                 self.attributes[name] = value
  131.                     
  132.                 if attrs.has_key(name+"-supported"):
  133.                     supported = attrs[name+"-supported"]
  134.                     self.possible_attributes[name] = (value, supported)
  135.             elif (not key.endswith ("-supported") and
  136.                   key != 'job-sheets-default' and
  137.                   key != 'printer-error-policy' and
  138.                   key != 'printer-op-policy' and
  139.                   not key.startswith ('requesting-user-name-')):
  140.                 self.other_attributes[key] = value
  141.         
  142.         self.job_sheet_start, self.job_sheet_end = attrs.get(
  143.             'job-sheets-default', ('none', 'none'))
  144.         self.job_sheets_supported = attrs.get('job-sheets-supported', ['none'])
  145.         self.error_policy = attrs.get('printer-error-policy', 'none')
  146.         self.error_policy_supported = attrs.get(
  147.             'printer-error-policy-supported', ['none'])
  148.         self.op_policy = attrs.get('printer-op-policy', "") or "default"
  149.         self.op_policy_supported = attrs.get(
  150.             'printer-op-policy-supported', ["default"])
  151.  
  152.         self.default_allow = True
  153.         self.except_users = []
  154.         if attrs.has_key('requesting-user-name-allowed'):
  155.             self.except_users = attrs['requesting-user-name-allowed']
  156.             self.default_allow = False
  157.         elif attrs.has_key('requesting-user-name-denied'):
  158.             self.except_users = attrs['requesting-user-name-denied']
  159.         self.except_users_string = ', '.join(self.except_users)
  160.         self.update (**attrs)
  161.  
  162.     def getServer(self):
  163.         """
  164.         Find out which server defines this printer.
  165.  
  166.         @returns: server URI or None
  167.         """
  168.         if not self.uri_supported[0].startswith('ipp://'):
  169.             return None
  170.         uri = self.uri_supported[0][6:]
  171.         uri = uri.split('/')[0]
  172.         uri = uri.split(':')[0]
  173.         if uri == "localhost.localdomain":
  174.             uri = "localhost"
  175.         return uri
  176.  
  177.     def getPPD(self):
  178.         """
  179.         Obtain the printer's PPD.
  180.  
  181.         @returns: cups.PPD object, or False for raw queues
  182.         @raise cups.IPPError: IPP error
  183.         """
  184.         result = None
  185.         if self._ppd is None:
  186.             try:
  187.                 self._ppd = self.connection.getPPD(self.name)
  188.                 result = cups.PPD (self._ppd)
  189.             except cups.IPPError, (e, m):
  190.                 if e == cups.IPP_NOT_FOUND:
  191.                     result = False
  192.                 else:
  193.                     raise
  194.  
  195.         if result != False and self._ppd != None:
  196.             result = cups.PPD (self._ppd)
  197.  
  198.         return result
  199.  
  200.     def setOption(self, name, value):
  201.         """
  202.         Set a printer's option.
  203.  
  204.         @param name: option name
  205.         @type name: string
  206.         @param value: option value
  207.         @type value: option-specific
  208.         """
  209.         if isinstance (value, float):
  210.             radixchar = locale.nl_langinfo (locale.RADIXCHAR)
  211.             if radixchar != '.':
  212.                 # Convert floats to strings, being careful with decimal points.
  213.                 value = str (value).replace (radixchar, '.')
  214.         self.connection.addPrinterOptionDefault(self.name, name, value)
  215.  
  216.     def unsetOption(self, name):
  217.         """
  218.         Unset a printer's option.
  219.  
  220.         @param name: option name
  221.         @type name: string
  222.         """
  223.         self.connection.deletePrinterOptionDefault(self.name, name)
  224.  
  225.     def setEnabled(self, on, reason=None):
  226.         """
  227.         Set the printer's enabled state.
  228.  
  229.         @param on: whether it will be enabled
  230.         @type on: bool
  231.         @param reason: reason for this state
  232.         @type reason: string
  233.         """
  234.         if on:
  235.             self.connection.enablePrinter(self.name)
  236.         else:
  237.             if reason:
  238.                 self.connection.disablePrinter(self.name, reason=reason)
  239.             else:
  240.                 self.connection.disablePrinter(self.name)
  241.  
  242.     def setAccepting(self, on, reason=None):
  243.         """
  244.         Set the printer's accepting state.
  245.  
  246.         @param on: whether it will be accepting
  247.         @type on: bool
  248.         @param reason: reason for this state
  249.         @type reason: string
  250.         """
  251.         if on:
  252.             self.connection.acceptJobs(self.name)
  253.         else:
  254.             if reason:
  255.                 self.connection.rejectJobs(self.name, reason=reason)
  256.             else:
  257.                 self.connection.rejectJobs(self.name)
  258.  
  259.     def setShared(self,on):
  260.         """
  261.         Set the printer's shared state.
  262.  
  263.         @param on: whether it will be accepting
  264.         @type on: bool
  265.         """
  266.         self.connection.setPrinterShared(self.name, on)
  267.  
  268.     def setErrorPolicy (self, policy):
  269.         """
  270.         Set the printer's error policy.
  271.  
  272.         @param policy: error policy
  273.         @type policy: string
  274.         """
  275.         self.connection.setPrinterErrorPolicy(self.name, policy)
  276.  
  277.     def setOperationPolicy(self, policy):
  278.         """
  279.         Set the printer's operation policy.
  280.  
  281.         @param policy: operation policy
  282.         @type policy: string
  283.         """
  284.         self.connection.setPrinterOpPolicy(self.name, policy)    
  285.  
  286.     def setJobSheets(self, start, end):
  287.         """
  288.         Set the printer's job sheets.
  289.  
  290.         @param start: start sheet
  291.         @type start: string
  292.         @param end: end sheet
  293.         @type end: string
  294.         """
  295.         self.connection.setPrinterJobSheets(self.name, start, end)
  296.  
  297.     def setAccess(self, allow, except_users):
  298.         """
  299.         Set access control list.
  300.  
  301.         @param allow: whether to allow by default, otherwise deny
  302.         @type allow: bool
  303.         @param except_users: exception list
  304.         @type except_users: string list
  305.         """
  306.         if isinstance(except_users, str):
  307.             users = except_users.split()
  308.             users = [u.split(",") for u in users]
  309.             except_users = []
  310.             for u in users:
  311.                 except_users.extend(u)
  312.             except_users = [u.strip() for u in except_users]
  313.             except_users = filter(None, except_users)
  314.             
  315.         if allow:
  316.             self.connection.setPrinterUsersDenied(self.name, except_users)
  317.         else:
  318.             self.connection.setPrinterUsersAllowed(self.name, except_users)
  319.  
  320.     def jobsQueued(self, only_tests=False, limit=None):
  321.         """
  322.         Find out whether jobs are queued for this printer.
  323.  
  324.         @param only_tests: whether to restrict search to test pages
  325.         @type only_tests: bool
  326.         @returns: list of job IDs
  327.         """
  328.         ret = []
  329.         try:
  330.             jobs = self.connection.getJobs ()
  331.         except cups.IPPError:
  332.             return ret
  333.  
  334.         for id, attrs in jobs.iteritems():
  335.             try:
  336.                 uri = attrs['job-printer-uri']
  337.                 uri = uri[uri.rindex ('/') + 1:]
  338.             except:
  339.                 continue
  340.             if uri != self.name:
  341.                 continue
  342.  
  343.             if (not only_tests or
  344.                 (attrs.has_key ('job-name') and
  345.                  attrs['job-name'] == 'Test Page')):
  346.                 ret.append (id)
  347.  
  348.                 if limit != None and len (ret) == limit:
  349.                     break
  350.         return ret
  351.  
  352.     def jobsPreserved(self, limit=None):
  353.         """
  354.         Find out whether there are preserved jobs for this printer.
  355.  
  356.         @return: list of job IDs
  357.         """
  358.         ret = []
  359.         try:
  360.             jobs = self.connection.getJobs (which_jobs='completed')
  361.         except cups.IPPError:
  362.             return ret
  363.  
  364.         for id, attrs in jobs.iteritems():
  365.             try:
  366.                 uri = attrs['job-printer-uri']
  367.                 uri = uri[uri.rindex ('/') + 1:]
  368.             except:
  369.                 continue
  370.             if uri != self.name:
  371.                 continue
  372.             if (attrs.get ('job-state',
  373.                            cups.IPP_JOB_PENDING) < cups.IPP_JOB_COMPLETED):
  374.                 continue
  375.             ret.append (id)
  376.             if limit != None and len (ret) == limit:
  377.                 break
  378.  
  379.         return ret
  380.  
  381.     def testsQueued(self, limit=None):
  382.         """
  383.         Find out whether test jobs are queued for this printer.
  384.  
  385.         @returns: list of job IDs
  386.         """
  387.         return self.jobsQueued (only_tests=True, limit=limit)
  388.  
  389.     def setAsDefault(self):
  390.         """
  391.         Set this printer as the system default.
  392.         """
  393.         self.connection.setDefault(self.name)
  394.  
  395.         # Also need to check system-wide lpoptions because that's how
  396.         # previous Fedora versions set the default (bug #217395).
  397.         (tmpfd, tmpfname) = tempfile.mkstemp ()
  398.         os.remove (tmpfname)
  399.         try:
  400.             resource = "/admin/conf/lpoptions"
  401.             self.connection.getFile(resource, fd=tmpfd)
  402.         except cups.HTTPError, (s,):
  403.             if s == cups.HTTP_NOT_FOUND:
  404.                 return False
  405.  
  406.             raise cups.HTTPError (s)
  407.  
  408.         f = os.fdopen (tmpfd, 'r+')
  409.         f.seek (0)
  410.         lines = f.readlines ()
  411.         changed = False
  412.         i = 0
  413.         for line in lines:
  414.             if line.startswith ("Default "):
  415.                 # This is the system-wide default.
  416.                 name = line.split (' ')[1]
  417.                 if name != self.name:
  418.                     # Stop it from over-riding the server default.
  419.                     lines[i] = "Dest " + line[8:]
  420.                     changed = True
  421.                 i += 1
  422.  
  423.         if changed:
  424.             f.seek (0)
  425.             f.writelines (lines)
  426.             f.truncate ()
  427.             os.lseek (tmpfd, 0, os.SEEK_SET)
  428.             try:
  429.                 self.connection.putFile (resource, fd=tmpfd)
  430.             except cups.HTTPError, (s,):
  431.                 return False
  432.  
  433.         return changed
  434.  
  435. def getPrinters(connection):
  436.     """
  437.     Obtain a list of printers.
  438.  
  439.     @param connection: CUPS connection
  440.     @type connection: CUPS.Connection object
  441.     @returns: L{Printer} list
  442.     """
  443.     printers = connection.getPrinters()
  444.     classes = connection.getClasses()
  445.     for name, printer in printers.iteritems():
  446.         printer = Printer(name, connection, **printer)
  447.         printers[name] = printer
  448.         if classes.has_key(name):
  449.             printer.class_members = classes[name]
  450.             printer.class_members.sort()
  451.     return printers
  452.  
  453. def parseDeviceID (id):
  454.     """
  455.     Parse an IEEE 1284 Device ID, so that it may be indexed by field name.
  456.  
  457.     @param id: IEEE 1284 Device ID, without the two leading length bytes
  458.     @type id: string
  459.     @returns: dict indexed by field name
  460.     """
  461.     id_dict = {}
  462.     pieces = id.split(";")
  463.     for piece in pieces:
  464.         if piece.find(":") == -1:
  465.             continue
  466.         name, value = piece.split(":",1)
  467.         id_dict[name] = value
  468.     if id_dict.has_key ("MANUFACTURER"):
  469.         id_dict.setdefault("MFG", id_dict["MANUFACTURER"])
  470.     if id_dict.has_key ("MODEL"):
  471.         id_dict.setdefault("MDL", id_dict["MODEL"])
  472.     if id_dict.has_key ("COMMAND SET"):
  473.         id_dict.setdefault("CMD", id_dict["COMMAND SET"])
  474.     for name in ["MFG", "MDL", "CMD", "CLS", "DES", "SN", "S", "P", "J"]:
  475.         id_dict.setdefault(name, "")
  476.     id_dict["CMD"] = id_dict["CMD"].split(',') 
  477.     return id_dict
  478.  
  479. class Device:
  480.     """
  481.     This class represents a CUPS device.
  482.     """
  483.  
  484.     def __init__(self, uri, **kw):
  485.         """
  486.         @param uri: device URI
  487.         @type uri: string
  488.         @param kw: device attributes
  489.         @type kw: dict
  490.         """
  491.         self.uri = uri
  492.         self.device_class = kw.get('device-class', 'Unknown') # XXX better default
  493.         self.info = kw.get('device-info', '')
  494.         self.make_and_model = kw.get('device-make-and-model', 'Unknown')
  495.         self.id = kw.get('device-id', '')
  496.  
  497.         uri_pieces = uri.split(":")
  498.         self.type =  uri_pieces[0]
  499.         self.is_class = len(uri_pieces)==1
  500.  
  501.         #self.id = 'MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;DES:Hewlett-Packard DeskJet 990C;SN:US05N1J00XLG;S:00808880800010032C1000000C2000000;P:0800,FL,B0;J:                    ;'
  502.  
  503.         self.id_dict = parseDeviceID (self.id)
  504.  
  505.         s = uri.find("serial=")
  506.         if s != -1 and not self.id_dict.get ('SN',''):
  507.             self.id_dict['SN'] = uri[s + 7:]
  508.  
  509.     def __repr__ (self):
  510.         return "<cupshelpers.Device \"%s\">" % self.uri
  511.  
  512.     def __cmp__(self, other):
  513.         """
  514.         Compare devices by order of preference.
  515.         """
  516.         if other == None:
  517.             return -1
  518.  
  519.         if self.is_class != other.is_class:
  520.             if other.is_class:
  521.                 return -1
  522.             return 1
  523.         if not self.is_class and (self.type != other.type):
  524.             # "hp"/"hpfax" before "usb" before * before "parallel" before
  525.             # "serial"
  526.             if other.type == "serial":
  527.                 return -1
  528.             if self.type == "serial":
  529.                 return 1
  530.             if other.type == "parallel":
  531.                 return -1
  532.             if self.type == "parallel":
  533.                 return 1
  534.             if other.type == "hp" or other.type == "hpfax":
  535.                 return 1
  536.             if self.type == "hp" or self.type == "hpfax":
  537.                 return -1
  538.             if other.type == "usb":
  539.                 return 1
  540.             if self.type == "usb":
  541.                 return -1
  542.         result = cmp(bool(self.id), bool(other.id))
  543.         if not result:
  544.             result = cmp(self.info, other.info)
  545.         
  546.         return result
  547.  
  548. def getDevices(connection):
  549.     """
  550.     Obtain a list of available CUPS devices.
  551.  
  552.     @param connection: CUPS connection
  553.     @type connection: cups.Connection object
  554.     @returns: a list of L{Device} objects
  555.     @raise cups.IPPError: IPP Error
  556.     """
  557.     devices = connection.getDevices()
  558.     for uri, data in devices.iteritems():
  559.         device = Device(uri, **data)
  560.         devices[uri] = device
  561.         if device.info != 'Unknown' and device.make_and_model == 'Unknown':
  562.             device.make_and_model = device.info
  563.     return devices
  564.  
  565. def activateNewPrinter(connection, name):
  566.     """
  567.     Set a new printer enabled, accepting jobs, and (if necessary) the
  568.     default printer.
  569.  
  570.     @param connection: CUPS connection
  571.     @type connection: cups.Connection object
  572.     @param name: printer name
  573.     @type name: string
  574.     @raise cups.IPPError: IPP error
  575.     """
  576.     connection.enablePrinter (name)
  577.     connection.acceptJobs (name)
  578.  
  579.     # Set as the default if there is not already a default printer.
  580.     if connection.getDefault () == None:
  581.         connection.setDefault (name)
  582.  
  583. def copyPPDOptions(ppd1, ppd2):
  584.     """
  585.     Copy default options between PPDs.
  586.  
  587.     @param ppd1: source PPD
  588.     @type ppd1: cups.PPD object
  589.     @param ppd2: destination PPD
  590.     @type ppd2: cups.PPD object
  591.     """
  592.     def getPPDGroupOptions(group):
  593.         options = group.options[:]
  594.         for g in group.subgroups:
  595.             options.extend(getPPDGroupOptions(g))
  596.         return options
  597.  
  598.     def iteratePPDOptions(ppd):
  599.         for group in ppd.optionGroups:
  600.             for option in getPPDGroupOptions(group):
  601.                 yield option
  602.  
  603.     for option in iteratePPDOptions(ppd1):
  604.         if option.keyword == "PageRegion":
  605.             continue
  606.         new_option = ppd2.findOption(option.keyword)
  607.         if new_option and option.ui==new_option.ui:
  608.             value = option.defchoice
  609.             for choice in new_option.choices:
  610.                 if choice["choice"]==value:
  611.                     ppd2.markOption(new_option.keyword, value)
  612.                     _debugprint ("set %s = %s" % (new_option.keyword, value))
  613.                     
  614. def setPPDPageSize(ppd, language):
  615.     """
  616.     Set the PPD page size according to locale.
  617.  
  618.     @param ppd: PPD
  619.     @type ppd: cups.PPD object
  620.     @param language: language, as given by the first element of
  621.     locale.setlocale
  622.     @type language: string
  623.     """
  624.     # Just set the page size to A4 or Letter, that's all.
  625.     # Use the same method CUPS uses.
  626.     size = 'A4'
  627.     letter = [ 'C', 'POSIX', 'en', 'en_US', 'en_CA', 'fr_CA' ]
  628.     for each in letter:
  629.         if language == each:
  630.             size = 'Letter'
  631.     try:
  632.         ppd.markOption ('PageSize', size)
  633.         _debugprint ("set PageSize = %s" % size)
  634.     except:
  635.         _debugprint ("Failed to set PageSize (%s not available?)" % size)
  636.  
  637. def missingPackagesAndExecutables(ppd):
  638.     """
  639.     Check that all relevant executables for a PPD are installed.
  640.  
  641.     @param ppd: PPD
  642.     @type ppd: cups.PPD object
  643.     @returns: string list pair, representing missing packages and
  644.     missing executables
  645.     """
  646.  
  647.     # First, a local function.  How to check that something exists
  648.     # in a path:
  649.     def pathcheck (name, path="/usr/bin:/bin"):
  650.         if name == "-":
  651.             # A filter of "-" means that no filter is required,
  652.             # i.e. the device accepts the given format as-is.
  653.             return "builtin"
  654.         # Strip out foomatic '%'-style place-holders.
  655.         p = name.find ('%')
  656.         if p != -1:
  657.             name = name[:p]
  658.         if len (name) == 0:
  659.             return "true"
  660.         if name[0] == '/':
  661.             if os.access (name, os.X_OK):
  662.                 _debugprint ("%s: found" % name)
  663.                 return name
  664.             else:
  665.                 _debugprint ("%s: NOT found" % name)
  666.                 return None
  667.         if name.find ("=") != -1:
  668.             return "builtin"
  669.         if name in [ ":", ".", "[", "alias", "bind", "break", "cd",
  670.                      "continue", "declare", "echo", "else", "eval",
  671.                      "exec", "exit", "export", "fi", "if", "kill", "let",
  672.                      "local", "popd", "printf", "pushd", "pwd", "read",
  673.                      "readonly", "set", "shift", "shopt", "source",
  674.                      "test", "then", "trap", "type", "ulimit", "umask",
  675.                      "unalias", "unset", "wait" ]:
  676.             return "builtin"
  677.         for component in path.split (':'):
  678.             file = component.rstrip (os.path.sep) + os.path.sep + name
  679.             if os.access (file, os.X_OK):
  680.                 _debugprint ("%s: found" % file)
  681.                 return file
  682.         _debugprint ("%s: NOT found in %s" % (name,path))
  683.         return None
  684.  
  685.     pkgs_to_install = []
  686.     exes_to_install = []
  687.  
  688.     # Find a 'FoomaticRIPCommandLine' attribute.
  689.     exe = exepath = None
  690.     attr = ppd.findAttr ('FoomaticRIPCommandLine')
  691.     if attr:
  692.         # Foomatic RIP command line to check.
  693.         cmdline = attr.value.replace ('&&\n', '')
  694.         cmdline = cmdline.replace ('"', '"')
  695.         cmdline = cmdline.replace ('<', '<')
  696.         cmdline = cmdline.replace ('>', '>')
  697.         if (cmdline.find ("(") != -1 or
  698.             cmdline.find ("&") != -1):
  699.             # Don't try to handle sub-shells or unreplaced HTML entities.
  700.             cmdline = ""
  701.  
  702.         # Strip out foomatic '%'-style place-holders
  703.         pipes = cmdline.split (';')
  704.         for pipe in pipes:
  705.             cmds = pipe.strip ().split ('|')
  706.             for cmd in cmds:
  707.                 args = cmd.strip ().split (' ')
  708.                 exe = args[0]
  709.                 exepath = pathcheck (exe)
  710.                 if not exepath:
  711.                     break
  712.  
  713.                 # Main executable found.  But if it's 'gs',
  714.                 # perhaps there is an IJS server we also need
  715.                 # to check.
  716.                 if os.path.basename (exepath) == 'gs':
  717.                     argn = len (args)
  718.                     argi = 1
  719.                     search = "-sIjsServer="
  720.                     while argi < argn:
  721.                         arg = args[argi]
  722.                         if arg.startswith (search):
  723.                             exe = arg[len (search):]
  724.                             exepath = pathcheck (exe)
  725.                             break
  726.  
  727.                         argi += 1
  728.  
  729.             if not exepath:
  730.                 # Next pipe.
  731.                 break
  732.  
  733.     if exepath or not exe:
  734.         # Look for '*cupsFilter' lines in the PPD and check that
  735.         # the filters are installed.
  736.         (tmpfd, tmpfname) = tempfile.mkstemp ()
  737.         os.unlink (tmpfname)
  738.         ppd.writeFd (tmpfd)
  739.         os.lseek (tmpfd, 0, os.SEEK_SET)
  740.         f = os.fdopen (tmpfd, "r")
  741.         search = "*cupsFilter:"
  742.         for line in f.readlines ():
  743.             if line.startswith (search):
  744.                 line = line[len (search):].strip ().strip ('"')
  745.                 try:
  746.                     (mimetype, cost, exe) = line.split (' ')
  747.                 except:
  748.                     continue
  749.  
  750.                 exepath = pathcheck (exe,
  751.                                      "/usr/lib/cups/filter:"
  752.                                      "/usr/lib64/cups/filter")
  753.  
  754.     if exe and not exepath:
  755.         # We didn't find a necessary executable.  Complain.
  756.  
  757.         # Strip out foomatic '%'-style place-holders.
  758.         p = exe.find ('%')
  759.         if p != -1:
  760.             exe = exe[:p]
  761.  
  762.         pkgs = {
  763.             # Foomatic command line executables
  764.             'gs': 'ghostscript',
  765.             'perl': 'perl',
  766.             'foo2oak-wrapper': None,
  767.             'pnm2ppa': 'pnm2ppa',
  768.             'c2050': 'c2050',
  769.             'c2070': 'c2070',
  770.             'cjet': 'cjet',
  771.             'lm1100': 'lx',
  772.             'esc-m': 'min12xxw',
  773.             'min12xxw': 'min12xxw',
  774.             'pbm2l2030': 'pbm2l2030',
  775.             'pbm2l7k': 'pbm2l7k',
  776.             'pbm2lex': 'pbm2l7k',
  777.             # IJS servers (used by foomatic)
  778.             'hpijs': 'hpijs',
  779.             'ijsgutenprint.5.0': 'gutenprint',
  780.             'ijsgutenprint.5.2': 'gutenprint',
  781.             # CUPS filters
  782.             'rastertogutenprint.5.0': 'gutenprint-cups',
  783.             'rastertogutenprint.5.2': 'gutenprint-cups',
  784.             'commandtoepson': 'gutenprint-cups',
  785.             'commandtocanon': 'gutenprint-cups',
  786.             }
  787.         try:
  788.             pkg = pkgs[exe]
  789.         except:
  790.             pkg = None
  791.  
  792.         if pkg:
  793.             _debugprint ("%s included in package %s" % (exe, pkg))
  794.             pkgs_to_install.append (pkg)
  795.         else:
  796.             exes_to_install.append (exe)
  797.  
  798.     return (pkgs_to_install, exes_to_install)
  799.  
  800. def _main():
  801.     c = cups.Connection()
  802.     #printers = getPrinters(c)
  803.     for device in getDevices(c).itervalues():
  804.         print device.uri, device.id_dict
  805.  
  806. if __name__=="__main__":
  807.     _main()
  808.